package com.bigdata.schaduler.client;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import okhttp3.*;
import org.apache.dolphinscheduler.common.enums.HttpMethod;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class DolphinClient {

  private final OkHttpClient client;
  private final String dolphinAddr;

  public DolphinClient(String token, String dolphinAddr, List<Interceptor> interceptors) {
    this.dolphinAddr = dolphinAddr;
    OkHttpClient.Builder builder = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .retryOnConnectionFailure(false)
        .addInterceptor(new TokenInterceptor(token))
        .addInterceptor(new LoggingInterceptor())
        .connectionPool(new ConnectionPool(50, 10, TimeUnit.MINUTES));
    if (CollUtil.isNotEmpty(interceptors)) {
      interceptors.forEach(builder::addInterceptor);
    }
    client = builder.build();
  }

  private String getBaseUrl() {
    return this.dolphinAddr;
  }

  private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json");

  public <T> T requestJson(HttpMethod method, String uri, Object params, Class<T> responseType) {
    Request request = buildJsonRequest(uri, params, method);
    return executeRequest(request, responseType);
  }

  public <T> T requestFormData(HttpMethod method, String uri, Object params, Class<T> responseType) {
    Request request = buildFormRequest(method, uri, params);
    return executeRequest(request, responseType);
  }

  private <T> T executeRequest(Request request, Class<T> responseType) {
    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful() || response.body() == null) {
        throw new DolphinException("IOException, responseCode = " + response.code());
      }

      TypeReference<Result<T>> typeReference = new TypeReference<Result<T>>() {};
      Result<T> result = JSONUtil.toBean(response.body().string(), typeReference, false);

      return result.getData(responseType);
    } catch (Exception e) {
      throw new DolphinException(e);
    }
  }

  private Request buildJsonRequest(String uri, Object params, HttpMethod method) {
    if (!HttpMethod.POST.equals(method) && !HttpMethod.PUT.equals(method)) {
      throw new DolphinException("application/json support method [POST|PUT]");
    }

    String url = getBaseUrl() + normalizeUri(uri);

    RequestBody body = params != null
        ? RequestBody.create(JSONUtil.toJsonStr(params), JSON_MEDIA_TYPE)
        : RequestBody.create("", JSON_MEDIA_TYPE);

    return new Request.Builder()
        .url(url)
        .method(method.name(), body)
        .build();
  }

  private Request buildFormRequest(HttpMethod method, String uri, Object params) {
    String url = getBaseUrl() + normalizeUri(uri);
    url = getFormDataUrl(method, url, params);

    Request.Builder builder = new Request.Builder().url(url);

    if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) || HttpMethod.DELETE.equals(method)) {
      if (Objects.isNull(params)) {
        builder.method(method.name(), RequestBody.create("", JSON_MEDIA_TYPE));
      } else {
        JSONObject jsonObject = JSONUtil.parseObj(params);
        FormBody.Builder formBuilder = new FormBody.Builder();
        for (String key : jsonObject.keySet()) {
          formBuilder.add(key, jsonObject.get(key).toString());
        }
        builder.method(method.name(), formBuilder.build());
      }
    } else if (HttpMethod.GET.equals(method)) {
      builder.method(method.name(), null);
    } else {
      throw new DolphinException("formData support method [GET|PUT|POST|DELETE]");
    }
    return builder.build();
  }

  private String getFormDataUrl(HttpMethod method, String url, Object params) {
    if (HttpMethod.GET.equals(method) && params != null) {
      UrlBuilder urlBuilder = UrlBuilder.of(url);
      JSONObject jsonObject = JSONUtil.parseObj(params);
      for (String key : jsonObject.keySet()) {
        Object value = jsonObject.get(key);
        if (value != null) {
          urlBuilder.addQuery(key, value);
        } else {
          urlBuilder.addQuery(key, "");
        }
      }
      url = urlBuilder.build();
    }
    return url;
  }

  private String normalizeUri(String uri) {
    return StrUtil.isEmpty(uri) ? "" : "/" + uri;
  }
}
